﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Configuration;
using System.IO;
using System.Xml.Serialization;
//using NBusiness.Providers;
//using NBusiness.Properties;
using System.Xml;
using System.Reflection;
using NBusiness.Data.Properties;

namespace NBusiness.Data
{
    /// <summary>
    /// The types of settings available to NBusiness.
    /// </summary>
    public enum SettingsType
    {
        ConnectionStringName,
        Provider,
        AuthorizeDefault,
        AccessDefault
    }

    /// <summary>
    /// 
    /// </summary>
    /// <example>
    /// &lt;?xml version="1.0"?&gt;
    /// &lt;configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"&gt;
	///     &lt;configSections&gt;
	///         &lt;section name="NBusiness" type="NBusiness.Data.Settings, NBusiness" /&gt;
	///     &lt;/configSections&gt;
    ///     &lt;NBusiness   ConnectionStringName="LocalSqlServer"
    ///                     AuthorizeDefault="False"
    ///                     AccessDefault="False"
    ///                     ProviderType="NBusiness.Providers.SqlDatabase, NBusiness"
    ///                     /&gt;
    /// &lt;/configuration&gt;
    /// </example>
    [XmlRoot("NBusiness")]
    public class Settings : IConfigurationSectionHandler
    {
        #region Member fields and properties
        private Dictionary<SettingsType, object> _defaultSettings = new Dictionary<SettingsType, object>();
        private Dictionary<string, Dictionary<SettingsType, object>> _typeSettings = new Dictionary<string, Dictionary<SettingsType, object>>();
        private Dictionary<string, Dictionary<SettingsType, object>> _assemblySettings = new Dictionary<string, Dictionary<SettingsType, object>>();
        #endregion

        public Settings()
        {
            _defaultSettings.Add(SettingsType.ConnectionStringName, "LocalSqlServer");

            // TODO: How to load a default provider?
            _defaultSettings.Add(SettingsType.Provider, null);

            _defaultSettings.Add(SettingsType.AuthorizeDefault, false);
            _defaultSettings.Add(SettingsType.AccessDefault, true);
        }


        public object GetSetting(SettingsType settingsType, Type callerType)
        {
            object value = null;
            if (_typeSettings.ContainsKey(callerType.FullName) &&
                _typeSettings[callerType.FullName].ContainsKey(settingsType))
            {
                value = _typeSettings[callerType.FullName][settingsType];
            }
            else if (_assemblySettings.ContainsKey(callerType.Assembly.FullName))
            {
                value = _typeSettings[callerType.Assembly.FullName][settingsType];
            }
            else
            {
                value = _defaultSettings[settingsType];
            }

            return value;
        }

        #region Singleton
        private static object SyncRoot = new object();
        private static Settings _instance;
        /// <summary>
        /// Singleton instance of the settings class. The settings are loaded out of the applications
        /// config file. See this Types documentation for an example of how to configure your config file.
        /// </summary>
        public static Settings Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncRoot)
                    {
                        if (_instance == null)
                        {
                            try
                            {
                                _instance = (Settings)ConfigurationManager.GetSection("NBusiness");
                            }
                            catch (ConfigurationErrorsException)
                            {
                                _instance = new Settings();
                            }

                            if (_instance == null)
                                _instance = new Settings();
                        }
                    }
                }
                return _instance;
            }
        }
        #endregion

        #region IConfigurationSectionHandler Members

        public object Create(object parent, object configContext, System.Xml.XmlNode section)
        {
            if (section == null)
                throw new ArgumentNullException(Resources.ConfigurationSectionMissing);

            LoadDefaultSettings(section);
            foreach (XmlNode node in section.ChildNodes)
            {
                if (node.Name == "Assemblies")
                {
                    LoadAssemblySettings(node.ChildNodes);
                }
                else if (node.Name == "Types")
                {
                    LoadTypeSettings(node.ChildNodes);
                }
            }

            return this;
        }


        private void LoadDefaultSettings(XmlNode node)
        {
            foreach (XmlAttribute attribute in node.Attributes)
            {
                SettingsType type = (SettingsType)Enum.Parse(typeof(SettingsType), attribute.Name);
                _defaultSettings[type] = ChangeValue(type, attribute.Value);
            }
        }

        private void LoadAssemblySettings(XmlNodeList xmlNodeList)
        {
            foreach (XmlNode node in xmlNodeList)
            {
                if (node.Name == "Add")
                {
                    string addAssembly = null;
                    Dictionary<SettingsType, object> attributeValues = new Dictionary<SettingsType, object>();
                    foreach (XmlAttribute attribute in node.Attributes)
                    {
                        if (attribute.Name == "Assembly")
                        {
                            addAssembly = attribute.Value;
                        }
                        else
                        {
                            SettingsType type = (SettingsType)Enum.Parse(typeof(SettingsType), attribute.Name);
                            attributeValues[type] = ChangeValue(type, attribute.Value);
                        }
                    }

                    foreach (SettingsType settingsType in attributeValues.Keys)
                    {
                        if (!_assemblySettings.ContainsKey(addAssembly))
                            _assemblySettings.Add(addAssembly, new Dictionary<SettingsType, object>());

                        _assemblySettings[addAssembly].Add(settingsType, attributeValues[settingsType]);
                    }
                }
            }
        }

        private void LoadTypeSettings(XmlNodeList xmlNodeList)
        {
            foreach (XmlNode node in xmlNodeList)
            {
                if (node.Name == "Add")
                {
                    string addType = null;
                    Dictionary<SettingsType, object> attributeValues = new Dictionary<SettingsType, object>();
                    foreach (XmlAttribute attribute in node.Attributes)
                    {
                        if (attribute.Name == "Type")
                        {
                            addType = attribute.Value;
                        }
                        else
                        {
                            SettingsType type = (SettingsType)Enum.Parse(typeof(SettingsType), attribute.Name);
                            attributeValues[type] = ChangeValue(type, attribute.Value);
                        }
                    }

                    foreach (SettingsType settingsType in attributeValues.Keys)
                    {
                        if (!_typeSettings.ContainsKey(addType))
                            _typeSettings.Add(addType, new Dictionary<SettingsType, object>());

                        _typeSettings[addType].Add(settingsType, attributeValues[settingsType]);
                    }
                }
            }
        }

        private object ChangeValue(SettingsType type, string value)
        {
            object newValue = null;
            switch (type)
            {
                case SettingsType.ConnectionStringName:
                    newValue = value;
                    break;
                case SettingsType.Provider:
                    newValue = Activator.CreateInstance(Type.GetType(value));
                    if (!(newValue is IDatabaseProvider))
                        throw new NotSupportedException("Provider must implement NBusiness.Data.IDatabaseProvider");
                    break;
                case SettingsType.AuthorizeDefault:
                    newValue = bool.Parse(value);
                    break;
                case SettingsType.AccessDefault:
                    newValue = bool.Parse(value);
                    break;
            }
            return newValue;
        }

        #endregion
    }
}
